#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "clausspilator.tab.h"
#include "fonctions.h"
#include "table_symboles.h"

void ecrire(const char * val)
{
    fprintf( yyout , "%s\n" , val );
}

void start() {
    // shebang perso
    ecrire("#!./run_spim\n");
    ecrire("\t.text");
    ecrire("main:");

    if(table_int == NULL) {
        table_int = create_table();
        table_int->addr = strdup("$s0");
        fprintf(yyout, "\tla %s, int\n", table_int->addr);
    }

    if(table_float == NULL) {
        table_float = create_table();
        table_float->addr = strdup("$s1");
        fprintf(yyout, "\tla %s, float\n", table_float->addr);
    }

    if(table_pointeur == NULL) {
        table_pointeur = create_table();
        table_pointeur->addr = strdup("$s2");
        fprintf(yyout, "\tla %s, pointeur\n", table_pointeur->addr);
    }

    if(table_string == NULL) {
        table_string = create_table();
    }


    // tableau des actions possible dans gencode, plus simple pour debugger
    strcpy( operation[EXIT] , "exit" );
    strcpy( operation[GOTO] , "goto" );
    strcpy( operation[NEG] , "neg" );
    strcpy( operation['+'] , "+" );
    strcpy( operation['*'] , "*" );
    strcpy( operation['/'] , "/" );
    strcpy( operation['='] , "=" );
    strcpy( operation[READ] , "read" );
    strcpy( operation[WRITE] , "write" );
    strcpy( operation[AND] , "&&" );
    strcpy( operation[OR] , "||" );
    strcpy( operation['>'] , ">" );
    strcpy( operation[GE] , ">=" );
    strcpy( operation['<'] , "<" );
    strcpy( operation[LE] , "<=" );
    strcpy( operation[EQ] , "==" );
    strcpy( operation[NE] , "!=" );
}

void end() {

    // generation du code de fin
    s_quad *tmp = gencode( EXIT , NULL , NULL , NULL , 0 );
    add_label( tmp->index );

    // initialisation au premier element
    s_quad_elem *tmp_elem = liste_quad->first;

    // print infos
#ifdef DEBUG
    printf("\nend of quad generation, result :\n\n");
    printf("ATTENTION : MODE DEBUG : aucun code n'est généré pour ne pas bloquer\n\n");
#endif

    // quad temporaire
    s_quad * tmp_quad;
    while(tmp_elem != NULL) {
       // printf("gencode\n");
        tmp_quad = tmp_elem->quad;
#ifdef DEBUG
        printf(" %2s\t: %7s , %7s , %7s\n", operation[tmp_quad->oper], (tmp_quad->arg1 == NULL) ? "_" : tmp_quad->arg1, (tmp_quad->arg2 == NULL) ? "_" : tmp_quad->arg2, (tmp_quad->res == NULL) ? "_" : tmp_quad->res);
#else
        gencode_final(tmp_quad);
#endif
        tmp_elem = tmp_elem->next;
    }


    fprintf(yyout, "\n\t.data\n");
    write_int(table_int, yyout);
    write_float(table_float, yyout);
    write_pointeur(table_pointeur, yyout);
    write_string(table_string, yyout);

    delete_table(table_int);
    delete_table(table_float);
    delete_table(table_pointeur);
    delete_table(table_string);

    fprintf(yyout, "nl: .asciiz \"\\n\"\n");

    printf("%s generated successfully\n", out );
}

s_expression *newstring(char *valeur) {
    s_expression *tmp_expr;

    s_cell *tmp_cell = add_cell(table_string, "", STRING);
    tmp_cell->chaine = strdup(valeur);

    tmp_expr = malloc(sizeof(s_expression));
    tmp_expr->ptr = malloc(8);
    sprintf(tmp_expr->ptr, "string%d", tmp_cell->index);
    tmp_expr->type = STRING;
    return tmp_expr;
}
s_expression *newint(int valeur) {
    s_expression *tmp_expr;

    s_cell *tmp_cell = add_cell(table_int, "", INT);
    tmp_cell->entier = valeur;

    tmp_expr = malloc(sizeof(s_expression));
    tmp_expr->ptr = malloc(8);
    sprintf(tmp_expr->ptr, "%d(%s)", tmp_cell->index * 4, table_int->addr);
    tmp_expr->type = INT;
    return tmp_expr;
}
s_expression *newfloat(float valeur) {
    s_expression *tmp_expr;

    s_cell *tmp_cell = add_cell(table_float, "", FLOAT);
    tmp_cell->reel = valeur;

    tmp_expr = malloc(sizeof(s_expression));
    tmp_expr->ptr = malloc(8);
    sprintf(tmp_expr->ptr, "%d(%s)", tmp_cell->index * 4, table_float->addr);
    tmp_expr->type = FLOAT;
    return tmp_expr;
}

s_expression *newtmp(int type) {
    if(type == INT) {
        return newtmpint();
    }
    else {
        return newtmpfloat();
    }
}
s_expression *newtmpint() {
    s_expression *tmp_expr;

    if(table_int == NULL) {
        table_int = create_table();
        table_int->addr = strdup((table_float == NULL) ? "$s0" : "$s1");
#ifdef DEBUG
        printf("table_int: %s\n", table_int->addr);
#endif
    }

    s_cell *tmp_cell = add_cell(table_int, "", INT);
    tmp_expr = malloc(sizeof(s_expression));
    tmp_expr->ptr = malloc(8);
    sprintf(tmp_expr->ptr, "%d(%s)", tmp_cell->index * 4, table_int->addr);
    tmp_expr->type = INT;
    return tmp_expr;
}
s_expression *newtmpfloat() {
    s_expression *tmp_expr;

    if(table_float == NULL) {
        table_float = create_table();
        table_float->addr = strdup((table_int == NULL) ? "$s0" : "$s1");
#ifdef DEBUG
        printf("table_float: %s\n", table_float->addr);
#endif
    }

    s_cell *tmp_cell = add_cell(table_float, "", FLOAT);

    tmp_expr = malloc(sizeof(s_expression));
    tmp_expr->ptr = malloc(8);
    sprintf(tmp_expr->ptr, "%d(%s)", tmp_cell->index * 4, table_float->addr);
    tmp_expr->type = FLOAT;
    return tmp_expr;
}
s_expression *id(s_ident *a_id) {
    s_expression *tmp_expr;

    s_cell *tmp_cell;
    if((tmp_cell = find_cell(table_pointeur, a_id->name)) != NULL) {
        tmp_expr = malloc(sizeof(s_expression));
        tmp_expr->ptr = malloc(8);

        tmp_expr->pointeur = tmp_cell->pointeur;
        sprintf(tmp_expr->ptr, "%d(%s)", tmp_cell->index * 4, table_pointeur->addr);
        tmp_expr->type = tmp_cell->type;
    }
    else if((tmp_cell = find_cell(table_int, a_id->name)) != NULL) {
        tmp_expr = malloc(sizeof(s_expression));
        tmp_expr->ptr = malloc(8);
        sprintf(tmp_expr->ptr, "%d(%s)", tmp_cell->index * 4, table_int->addr);
        tmp_expr->type = INT;
    }
    else if ((tmp_cell = find_cell(table_float, a_id->name)) != NULL) {
        tmp_expr = malloc(sizeof(s_expression));
        tmp_expr->ptr = malloc(8);
        sprintf(tmp_expr->ptr, "%d(%s)", tmp_cell->index * 4, table_float->addr);
        tmp_expr->type = FLOAT;
    }
    else {
        yyerror("undeclared variable");
    }

    return tmp_expr;
}

s_quad * get_quad( int index )
{
    s_quad_elem * courant = liste_quad->last;

    while( courant != NULL )
    {
        if ( courant->quad->index == index )
            return courant->quad;
        courant = courant->previous;
    }

}

void add_label(int index) {
    s_label *tmp_label = malloc(sizeof(s_label));
    tmp_label->previous = NULL;
    tmp_label->next = NULL;

    tmp_label->num_quad = index;

    if(labels == NULL) {
        labels = malloc(sizeof(s_list_labels));
    }
    if(labels->first == NULL) {
        labels->first = tmp_label;
    }
    if(labels->last != NULL) {
        tmp_label->previous = labels->last;
        labels->last->next = tmp_label;
    }

    labels->last = tmp_label;
}

int is_label(int index)
{
    if(labels != NULL)
    {
        s_label *label = labels->first;
        while(label != NULL)
        {
            if(label->num_quad == index)
            {
                /*
                if(label->previous == NULL)
                    labels->first = label->next;
                if(label->next == NULL)
                    labels->last = label->previous;

                label->previous->next = label->next;
                label->next->previous = label->previous;
                free(label);
*/
                return 1;
            }
            label = label->next;
        }
    }
    return 0;
}

s_quad *gencode(int oper, const char *arg1, const char *arg2, const char *res, int type) {
#ifdef DEBUG
    printf(" %2s\t: %7s , %7s , %7s\n", operation[oper], (arg1 == NULL) ? "_" : arg1, (arg2 == NULL) ? "_" : arg2, (res == NULL) ? "_" : res);
#endif
    if(liste_quad == NULL) {
        liste_quad = malloc(sizeof(list_quad));
    }

    // si on fait un goto vers un label non null
    // on l'ajoute a notre liste de label a print
    if ( oper == GOTO && res != NULL )
    {
        int label;
        sscanf( res , "l%d" , &label );
        add_label( label );
    }

    s_quad* nouv = malloc( sizeof( s_quad ));
    nouv->oper = oper;
    nouv->arg1 = (arg1 != NULL) ? strdup( arg1 ) : NULL;
    nouv->arg2 = (arg2 != NULL) ? strdup( arg2 ) : NULL;
    nouv->res = (res != NULL) ? strdup(res) : NULL;
    nouv->type = type;

    s_quad_elem* nouv_elem = malloc( sizeof( s_quad_elem ));
    nouv_elem->previous = NULL;
    nouv_elem->next = NULL;
    nouv_elem->quad = nouv;

    // linkage
    // on lui donne un index
    if(liste_quad->first == NULL) {
        liste_quad->first = nouv_elem;
    }

    if(liste_quad->last == NULL) {
        nouv->index = 0;
    }
    else {
        liste_quad->last->next = nouv_elem;
        nouv->index = liste_quad->last->quad->index + 1;
        nouv_elem->previous = liste_quad->last;
    }

    liste_quad->last = nouv_elem;

    return nouv;
}
s_quad *gencode_pointer(int oper, const char *arg1, const char *arg2, const char *res, int type, int arg1_pointeur, int arg2_pointeur, int res_pointeur) {
#ifdef DEBUG
    printf(" %2s\t: %7s , %7s , %7s\n", operation[oper], (arg1 == NULL) ? "_" : arg1, (arg2 == NULL) ? "_" : arg2, (res == NULL) ? "_" : res);
#endif
    if(liste_quad == NULL) {
        liste_quad = malloc(sizeof(list_quad));
    }

    // si on fait un goto vers un label non null
    // on l'ajoute a notre liste de label a print
    if ( oper == GOTO && res != NULL )
    {
        int label;
        sscanf( res , "l%d" , &label );
        add_label( label );
    }

    s_quad* nouv = malloc( sizeof( s_quad ));
    nouv->oper = oper;
    nouv->arg1 = (arg1 != NULL) ? strdup( arg1 ) : NULL;
    nouv->arg2 = (arg2 != NULL) ? strdup( arg2 ) : NULL;
    nouv->res = (res != NULL) ? strdup(res) : NULL;
    nouv->type = type;
    nouv->arg1_pointeur = arg1_pointeur;
    nouv->arg2_pointeur = arg2_pointeur;
    nouv->res_pointeur = res_pointeur;

    s_quad_elem* nouv_elem = malloc( sizeof( s_quad_elem ));
    nouv_elem->previous = NULL;
    nouv_elem->next = NULL;
    nouv_elem->quad = nouv;

    // linkage
    // on lui donne un index
    if(liste_quad->first == NULL) {
        liste_quad->first = nouv_elem;
    }

    if(liste_quad->last == NULL) {
        nouv->index = 0;
    }
    else {
        liste_quad->last->next = nouv_elem;
        nouv->index = liste_quad->last->quad->index + 1;
        nouv_elem->previous = liste_quad->last;
    }

    liste_quad->last = nouv_elem;

    return nouv;
}

int gencode_final(s_quad *q) {
    fprintf(yyout, "# %s\n", operation[q->oper]);
    if(is_label(q->index)) {
        fprintf(yyout, "l%d:\n", q->index);
    }
    if(q->arg1_pointeur > 0) {
        fprintf(yyout, "\tlw $t5, %s\n", q->arg1);

        int i;
        for(i = 0; i < q->arg1_pointeur - 1; i++) {
            fprintf(yyout, "\tlw $t5, ($t5)\n");
        }
        q->arg1 = strdup("($t5)");
    }
    if(q->arg2_pointeur > 0) {
        fprintf(yyout, "\tlw $t6, %s\n", q->arg2);

        int i;
        for(i = 0; i < q->arg2_pointeur - 1; i++) {
            fprintf(yyout, "\tlw $t6, ($t6)\n");
        }
        q->arg1 = strdup("($t6)");
    }
    if(q->res_pointeur > 0) {
        fprintf(yyout, "\tlw $t7, %s\n", q->res);

        int i;
        for(i = 0; i < q->res_pointeur - 1; i++) {
            fprintf(yyout, "\tlw $t7, ($t7)\n");
        }
        q->res = strdup("($t7)");
    }

    switch(q->oper) {
        case EXIT:
            ecrire("\tli $v0,10");
            ecrire("\tsyscall");
            break;
        case GOTO:
            fprintf(yyout, "\tb %s\n", q->res);
            break;
        case NEG:
            if(q->type == FLOAT) {
                fprintf(yyout, "\tl.s $f2, %s\n", q->arg1);
                fprintf(yyout, "\tneg.s $f2, $f2\n");
                fprintf(yyout, "\ts.s $f2, %s\n", q->res);
            }
            else {
                fprintf(yyout, "\tlw $t2, %s\n", q->arg1);
                fprintf(yyout, "\tneg $t2, $t2\n");
                fprintf(yyout, "\tsw $t2, %s\n", q->res);
            }
            break;
        case '+':
        case '-':
        case '*':
        case '/':
        case '%':
            if(q->type == FLOAT) {
                fprintf(yyout, "\tl.s $f2, %s\n", q->arg1);
                fprintf(yyout, "\tl.s $f3, %s\n", q->arg2);
                fprintf(yyout, "\t");
                switch(q->oper) {
                    case '+':
                        fprintf(yyout, "add.s");
                        break;
                    case '-':
                        fprintf(yyout, "sub.s");
                        break;
                    case '*':
                        fprintf(yyout, "mul.s");
                        break;
                    case '/':
                        fprintf(yyout, "div.s");
                        break;
                }
                fprintf(yyout, " $f2, $f2, $f3\n");
                fprintf(yyout, "\ts.s $f2, %s\n", q->res);
            }
            else {
                fprintf(yyout, "\tlw $t2, %s\n", q->arg1);
                fprintf(yyout, "\tlw $t3, %s\n", q->arg2);
                fprintf(yyout, "\t");
                switch(q->oper) {
                    case '+':
                        fprintf(yyout, "add");
                        break;
                    case '-':
                        fprintf(yyout, "sub");
                        break;
                    case '*':
                        fprintf(yyout, "mul");
                        break;
                    case '/':
                        fprintf(yyout, "div");
                        break;
                    case '%':
                        fprintf(yyout, "rem");
                        break;
                }
                fprintf(yyout, " $t2, $t2, $t3\n");
                fprintf(yyout, "\tsw $t2, %s\n", q->res);
            }
            break;
        case '=':
            fprintf(yyout, "\t%s2, %s\n", (q->type == FLOAT) ? "l.s $f" : (q->type == INT) ? "lw $t" : "la $t", q->arg1);
            fprintf(yyout, "\t%s2, %s\n", (q->type == FLOAT) ? "s.s $f" : "sw $t", q->res);
            break;
        case READ:
            fprintf(yyout, "\tli $v0, %d\n\tsyscall\n\t%s, %s\n\n", (q->type == FLOAT) ? 6 : 5, (q->type == FLOAT) ? "s.s $f0" : "sw $v0", q->arg1);
            break;
        case WRITE:
            if(q->type == STRING) {
                fprintf(yyout, "\tli $v0, 4\n\tla $a0, %s\n\tsyscall\n\n", q->arg1);
            }
            else {
                fprintf(yyout, "\tli $v0, %d\n\t%s, %s\n\tsyscall\n\n", (q->type == FLOAT) ? 2 : 1, (q->type == FLOAT) ? "l.s $f12" : "lw $a0", q->arg1);
                fprintf(yyout, "\tli $v0, 4\n\tla $a0, nl\n\tsyscall\n\n");
            }
            break;
        case AND:
        case OR:
            fprintf(yyout, "\tlw $t2, %s\n", q->arg1);
            fprintf(yyout, "\tlw $t3, %s\n", q->arg2);
            fprintf(yyout, "\t%s $t2, $t2, $t3\n",( q->oper == AND )? "and" : "or" );
            fprintf(yyout, "\tsw $t2, %s\n", q->res);
            break;
        case '>':
        case GE:
        case '<':
        case LE:
        case EQ:
        case NE:
            fprintf(yyout, "\tlw $t2, %s\n", q->arg1);
            fprintf(yyout, "\tlw $t3, %s\n\t", q->arg2);
            switch( q->oper )
            {
                case '>':
                    fprintf( yyout , "bgt" );
                    break;
                case GE:
                    fprintf( yyout , "bge" );
                    break;
                case '<':
                    fprintf( yyout , "blt" );
                    break;
                case LE:
                    fprintf( yyout , "ble" );
                    break;
                case EQ:
                    fprintf( yyout , "beq" );
                    break;
                case NE:
                    fprintf( yyout , "bne" );
                    break;
            }
            // a voir, comment renvoyer vers un point quad
            fprintf(yyout, " $t2, $t3, %s\n" , q->res );
            break;
    }
//    fprintf(yyout, "\n");
}

// creer une liste de quadruplets avec un seul element
list_quad *creeListe( s_quad* q )
{
#ifdef DEBUG
    // DEBUG
    printf("calling creeListe : \n" );
    afficher_listequad();
#endif

    s_quad_elem * elem = malloc ( sizeof( s_quad_elem ));
    elem->quad = q;
    elem->next = NULL;
    elem->previous = NULL;

    list_quad * liste = malloc( sizeof( list_quad ));
    liste->first = elem;
    liste->last = elem;

#ifdef DEBUG
    afficher_listequad();
#endif
    return liste;
}

// reatribution de l'etiquette de branchement
void complete( list_quad * lq , int num_label )
{
#ifdef DEBUG
    // DEBUG
    printf("calling complete : %d \n" , num_label);
    afficher_listequad();
#endif

    // possible dans le cas d'un while qui n'a pas de sous instructions en if else etc ...
    if ( lq == NULL )
        return;

    // on peut aller vers ce label, donc on l'ajotue a la liste des labels
    add_label( num_label );

    // et on complete tout ce qui doit aller vers cet endroit
    s_quad_elem *courant_elem = lq->first;
    s_quad * courant;
    while ( courant_elem != NULL )
    {
        courant = courant_elem->quad;
        // reatribution du label au res
        if ( courant->res == NULL
            &&
            (
                courant->oper == GOTO
                || courant->oper == '>'
                || courant->oper == GE
                || courant->oper =='<'
                || courant->oper == LE
                || courant->oper == EQ
                || courant->oper == NE
            )
           )
        {
            // on cree notre label
            char *label = malloc(20);
            sprintf( label , "l%d" , num_label );
            courant->res = strdup(label);
            free( label );
        }
        // next iteration
        courant_elem = courant_elem->next;
    }
#ifdef DEBUG
    afficher_listequad();
#endif
}

// concatene deux listes de quadruplets
list_quad *concat( list_quad* lq1, list_quad* lq2 )
{
    // DEBUG
#ifdef DEBUG
    printf("calling concat \n");
    afficher_listequad();
#endif

    // si une liste est null, nous on l'est pas, on retourne l'autre
    if ( lq1 == NULL && lq2 != NULL )
        return lq2;
    else if ( lq2 == NULL && lq1 != NULL )
        return lq1;
    else if ( lq1 == NULL && lq2 == NULL )
        return NULL;

    // on colle la fin de la premiere liste au debut de la 2eme
    lq1->last->next = lq2->first;
    lq2->first->previous = lq1->last;
    lq1->last = lq2->last;
    // on libère la mémoire de lq2
    free(lq2);
    // on retourne notre nouveau lq1
    return lq1;
}

//uniquement pour du debug
void afficher_listequad()
{
    return;
    s_quad_elem *tmp_elem = liste_quad->first;
    s_quad *tmp_quad;
    printf("=======\n");
    while(tmp_elem != NULL)
    {
        tmp_quad = tmp_elem->quad;
        printf(" %2s\t: %7s , %7s , %7s\n", operation[tmp_quad->oper], (tmp_quad->arg1 == NULL) ? "_" : tmp_quad->arg1, (tmp_quad->arg2 == NULL) ? "_" : tmp_quad->arg2, (tmp_quad->res == NULL) ? "_" : tmp_quad->res);
        tmp_elem = tmp_elem->next;
    }
    printf("=======\n");
}

void afficher_liste( list_quad* l )
{
    s_quad_elem *tmp_elem = l->first;
    s_quad *tmp_quad;
    printf("=== debut liste speciale ===\n");
    while(tmp_elem != NULL)
    {
        tmp_quad = tmp_elem->quad;
        printf(" %2s\t: %7s , %7s , %7s\n", operation[tmp_quad->oper], (tmp_quad->arg1 == NULL) ? "_" : tmp_quad->arg1, (tmp_quad->arg2 == NULL) ? "_" : tmp_quad->arg2, (tmp_quad->res == NULL) ? "_" : tmp_quad->res);
        tmp_elem = tmp_elem->next;
    }
    printf("=== fin liste speciale ===\n");
}

int nextquad()
{
    return liste_quad->last->quad->index + 1;
}

char* out_file( char* arg )
{
    int t = strlen( arg );
    char *init = strdup( arg );
    if ( init[t-2] == '.' )
        init[t-2] = '\0';
    else if ( init[t-3] == '.' )
        init[t-3] = '\0';
    else if ( init[t-4] == '.' )
        init[t-4] = '\0';
    char *fichier = malloc( t );
    sprintf( fichier , "%s.s" , init );
    free( init );
    return fichier;
}
